package jsfind

import (
	"blot/base"
	"blot/config"
	"blot/structural"
	"errors"
	"fmt"
	"github.com/dlclark/regexp2"
	"github.com/fatih/color"
	"regexp"
	"runtime"
	"strings"
	"sync"
)

var (
	ord = make(chan string)

	l                 sync.Mutex
	jsurl             = make(map[string]bool)
	funzz             = config.Read_fuzz()
	sensitive_url     = make(map[string][]any)
	sensitive_domname = make(map[string][]any)
	body              structural.Body
	w                 sync.WaitGroup
	JsRequest         *base.HTTPClient
)

var jss = regexp2.MustCompile("/*\\.js", 0)
var locks chan int

func Start() {

	locks = make(chan int, structural.T)

	html := <-structural.Html
	MapData := base.Html_url(html)

	w.Add(1)
	go go_th()
	http_js(MapData)

	w.Wait()
	if structural.S {
		fturl()
	}
	Tem_codesize()

}

// chanlgo协程很聪明，阻塞之后它就主动交出cpu，相当于调用runtime.Gosched()，让其他协程去执行，希望其他协程能帮自己解除阻塞（当然是通过读写管道的方式）
// 如果阻塞发生在main协程里，并且没有其他子协程可以执行，那就可以确定“希望永远等不来”，自已把自己杀掉，报一个fatal error:deadlock出来
// 也就是说一个协程当中chanl不能同时读写，主线程chanl如果阻塞，会去找子线程解决这个阻塞，如果子线程不能解决会产生死锁，
// 线程执行逻辑是：先主线程然后才是子线程，所以阻塞就是为了启动子线程
func go_th() {
	for {
		select {
		case data := <-ord:
			locks <- 1
			w.Add(1)
			go js_requ(data)
		}
	}

}

func http_js(data map[string]bool) {
	defer w.Done()
	for k := range data {
		http_ := strings.Split(k, ":")[0]
		https := strings.Split(k, ":")[0]
		if https == "https" || http_ == "http" {
			if strings.Split(k, "/")[2] == structural.URL.Domname {
				w.Add(1)
				go fuzz(k, 1)
			} else if okk, _ := jss.MatchString(k); okk {
				//w.Add(1)
				ord <- k
			} else {
				w.Add(1)
				go fuzz(k, 2)
			}
		} else if ok, _ := jss.MatchString(k); ok {
			//提取js

			ord <- k
		} else {
			w.Add(1)
			go fuzz(k, 1)
		}
	}

}

func js_requ(data string) {
	defer w.Done()
	defer func() {
		if err_re := recover(); err_re != nil {
			runtime.Goexit()
		}
	}()
	var resp []byte
	l.Lock()
	jsurl[data] = true
	l.Unlock()
	if strings.HasPrefix(data, "http") {
		resp, _, _, _ = JsRequest.Request(data, body)
	} else {
		if strings.HasPrefix(data, "/") {
			resp, _, _, _ = JsRequest.Request(structural.URL.Url+data, body)
		} else {
			resp, _, _, _ = JsRequest.Request(structural.URL.Url+"/"+data, body)
		}
	}
	<-locks
	url_js(string(resp))
}

func jscontext(context string) []string {
	var path_data []string
	//提取js文件中的.js和/xxx路径
	//作废recom := regexp2.MustCompile("(?<='|\")#/[a-zA-Z].+?(?='|\")|(?<='|\")/[a-zA-Z].+?(?='|\")|(https?|http|ftp|file):\\/\\/[-A-Za-z0-9+&@#/%?=~_|!:,.;]+[-A-Za-z0-9+&@#/%=~_|]", 0) //(?<='|")/[a-zA-Z].+?(?='|")
	//作废recom := regexp2.MustCompile("(?<='|\")#/[a-zA-Z].+?(?='|\")|(?<='|\")/[a-zA-Z].+?(?='|\")|(https?|http|ftp|file):\\/\\/[-A-Za-z0-9+&@#/%?=~_|!:,.;]+[-A-Za-z0-9+&@#/%=~_|]|(?<=href=\"|src=\"|src='|href=').+?(?= |>|\"|')", 0)
	//recom := regexp2.MustCompile("(?<='|\")#/[a-zA-Z0-9].+?(?='|\")|(?<='|\")/[a-zA-Z0-9].+?(?='|\")|(https?|http|ftp|file):\\/\\/[-A-Za-z0-9+&@#/%?=~_|!:,.;]+[-A-Za-z0-9+&@#/%=~_|]|(?<=href=\"|src=\"|src='|href=').+?(?= |>|\"|')", 0)
	recom := regexp2.MustCompile(`(?i)(?<='|")#/[a-z0-9].*?(?='|")|(?<='|")/[a-z0-9].*?(?='|")|(?:(?:https?|ftp|file):\/\/|www\.)[\w-]+(\.[\w-]+)+([\w.,@?^=%&:/~+#-]*[\w@?^=%&/~+#-])?`, 0)
	m, _ := recom.FindStringMatch(context)
	for m != nil {
		path_data = append(path_data, m.String())
		m, _ = recom.FindNextMatch(m)
	}

	return path_data
}

var (
	rejs  = regexp2.MustCompile("/*\\.js", 0)
	recss = regexp2.MustCompile("(?<=)\\.(css|png|jpg|ico)", 0)
	//rehttp = regexp2.MustCompile("(?<=https?://)[^/].+?(?=/|\"|')", 0) //提取域名
)

func url_js(conext string) {

	for _, value := range jscontext(conext) {
		if ok, _ := rejs.MatchString(value); ok {
			if okjs, _ := jsurl[value]; okjs {
				continue
			}
			ord <- value
		} else if ok, _ := recss.MatchString(value); ok {
			continue
		} else if strings.HasPrefix(value, "https") || strings.HasPrefix(value, "http") {
			if strings.Split(value, "/")[2] == structural.URL.Domname {
				w.Add(1)
				go fuzz(value, 1)
			} else {
				w.Add(1)
				go fuzz(value, 2)
			}
		} else {

			w.Add(1)
			go fuzz(value, 1)
		}
	}
}
func fturl() {
	//url

	defer w.Done()
	fmt.Println("目标资产:")
	fmt.Println(len(sensitive_url))
	for k := range sensitive_url {
		color.Green(k + "\n")
	}
	w.Add(1)
	go domname()
}

func domname() {
	//domname
	defer w.Done()
	fmt.Println("其他域名资产:")
	for k := range sensitive_domname {
		color.Green(k + "\n")
	}
	w.Add(1)
	go ftjs()
}
func ftjs() {
	//js
	defer w.Done()
	fmt.Println("js资产:")
	for k := range jsurl {
		color.Green(k + "\n")
	}
}

var github *regexp2.Regexp = regexp2.MustCompile("github.com", 0)

// 当循环中存在子线程时，在循环时间段内相当于阻塞所以会启动子线程
func codesize(k string, code int) {

	w.Add(1)
	var code_size []any
	var text []byte
	var status_code int
	defer w.Done()
	if ok, _ := github.MatchString(k); ok {
		goto skip
	}

	if strings.HasPrefix(k, "http") {
		text, status_code, _, _ = JsRequest.Request(k, body)
	} else {
		if strings.HasPrefix(k, "/") {
			text, status_code, _, _ = JsRequest.Request(structural.URL.Url+k, body)
		} else {
			text, status_code, _, _ = JsRequest.Request(structural.URL.Url+"/"+k, body)

		}
	}

skip:
	<-locks
	if code == 1 {
		code_size = append(code_size, status_code, len(string(text)), sensitive_url[k])
		l.Lock()
		sensitive_url[k] = code_size
		//fmt.Println(sensitive_url)
		l.Unlock()

	} else if code == 2 {
		code_size = append(code_size, status_code, len(string(text)), sensitive_domname[k])
		l.Lock()
		sensitive_domname[k] = code_size
		l.Unlock()
	}

}
func Tem_codesize() {

	go func() {
		defer w.Done()

		for k := range sensitive_url {
			locks <- 1
			go codesize(k, 1)
		}

	}()
	go func() {
		defer w.Done()
		for K := range sensitive_domname {
			locks <- 1
			go codesize(K, 2)
		}
	}()
	w.Add(2)
	w.Wait()
	if len(sensitive_url) == 0 && len(sensitive_domname) == 0 {
		panic(errors.New("未发现url和域名，不生成文档"))
	}
	config.HtmlData.URLDATA = sensitive_url
	config.HtmlData.DomName = sensitive_domname
	config.HtmlData.JsData = jsurl
	config.Create_html(structural.I, structural.URL.Url, 1)
}

var rec *regexp2.Regexp = regexp2.MustCompile("data:image/|,|>|<|'|www.w3.org|==", 0)

func fuzz(url string, panduan int) {
	//匹配敏感字符、

	defer w.Done()
	var sensitive []any

	if ok, _ := rec.MatchString(url); !ok {
		for _, value := range funzz {
			if ok, _ := regexp.MatchString(".*"+value+".*", url); ok {
				sensitive = append(sensitive, value)
			}
		}
		if panduan == 1 {
			l.Lock()
			sensitive_url[url] = sensitive //网址
			l.Unlock()

		} else if panduan == 2 {
			l.Lock()
			sensitive_domname[url] = sensitive //其他域名
			l.Unlock()
		}
	}
}
